﻿/*********************************************************************************
**                                                                              **
**  Copyright (C) 2024 LiLong                                                   **
**  This file is part of IonexFileReader.                                       **
**                                                                              **
**  IonexFileReader is free software: you can redistribute it and/or modify     **
**  it under the terms of the GNU General Public License as published by        **
**  the Free Software Foundation, either version 3 of the License, or           **
**  (at your option) any later version.                                         **
**                                                                              **
**  IonexFileReader is distributed in the hope that it will be useful,          **
**  but WITHOUT ANY WARRANTY; without even the implied warranty of              **
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               **
**  GNU General Public License for more details.                                **
**                                                                              **
**  You should have received a copy of the GNU General Public License           **
**  along with IonexFileReader. If not, see <https://www.gnu.org/licenses/>.    **
**********************************************************************************/
#pragma once

#include "../IonexFile.h"

#include <tao/pegtl.hpp>

#include <chrono>

namespace Ionex
{
struct IonexPaser
{
    //  IONEX VERSION / TYPE
    double version;
    std::string fileType { "IONOSPHERE MAPS" };
    SatelliteSystem system;
    // PGM / RUN BY / DATE
    std::string program;
    std::string agency;
    std::string createDateTime;
    // DESCRIPTION
    std::vector<std::string> descriptions;
    // COMMENT
    std::vector<std::string> comments;
    // EPOCH OF FIRST MAP
    DateTime epochOfFirstMap;
    // EPOCH OF LAST MAP
    DateTime epochOfLastMap;
    // INTERVAL
    int interval;
    // # OF MAPS IN FILE
    int ofMapsInFile;
    // MAPPING FUNCTION
    MapingFunction mapingFunction;
    // ELEVATION CUTOFF
    double elevationCutOff;
    // OBSERVABLES USED
    std::string observablesUsed;
    // # OF STATIONS
    std::optional<int> ofStations;
    // # OF SATELLITES
    std::optional<int> ofSatellites;
    // SYS / # STA / # SAT
    // TODO
    // BASE RADIUS
    double baseRadius;
    // MAP DIMENSION
    int mapDimension;
    // HGT1 / HGT2 / DHGT
    double hgt1, hgt2, dhgt;
    // LAT1 / LAT2 / DLAT
    double lat1, lat2, dlat;
    // LON1 / LON2 / DLON
    double lon1, lon2, dlon;
    // EXPONENT
    std::optional<int> exponent;
    // TEC MAP
    std::vector<DataMap> tecMaps;
    // RMS MAP
    std::vector<DataMap> rmsMaps;
    // HEIGHT MAP
    std::vector<DataMap> heightMaps;

    struct ParseState
    {
        DateTime dateTime;
        DataMap dataMap;
    } cache;
};

namespace PEG
{
using namespace tao::pegtl;

// clang-format off

// Common
struct Space : star<one<' '>> {};
struct Float : seq<opt<one<'-'>>, plus<digit>, one<'.'>, plus<digit>> {};
struct Int : seq<opt<one<'-'>>, plus<digit>> {};
struct Year : plus<digit> {};
struct Month : plus<digit> {};
struct Day : plus<digit> {};
struct Hour : plus<digit> {};
struct Minute : plus<digit> {};
struct Second : plus<digit> {};
struct EPOCH : seq<Space, Year, Space, Month, Space, Day, Space, Hour, Space, Minute, Space, Second> {};
using LineTail = sor<string<'\r','\n'>, one<'\n'>>;
//

#define IONEX_KEYWORDS(x) seq<TAO_PEGTL_STRING(x), LineTail>

struct Version : seq<Space, Float> {};
struct Type : one<'I'> {};
struct Satellite : rep<3, ascii::alpha> {};
struct IONEX_VERSION_TYPE : until<IONEX_KEYWORDS("IONEX VERSION / TYPE"), Version, rep<12, one<' '>>, Type, rep<19, ascii::any>, Satellite, rep<17, one<' '>>> {};
struct PGM_RUNBY_DATE : seq<rep<60, ascii::any>, IONEX_KEYWORDS("PGM / RUN BY / DATE ")> {};
struct COMMENT : seq<rep<60, ascii::any>, IONEX_KEYWORDS("COMMENT             ")> {};
struct DESCRIPTION : seq<rep<60, ascii::any>, IONEX_KEYWORDS("DESCRIPTION         ")> {};
struct EPOCH_OF_FIRST_MAP : seq<EPOCH, Space, IONEX_KEYWORDS("EPOCH OF FIRST MAP  ")> {};
struct EPOCH_OF_LAST_MAP : seq<EPOCH, Space, IONEX_KEYWORDS("EPOCH OF LAST MAP   ")> {};
struct INTERVAL : seq<Space, Int, Space, IONEX_KEYWORDS("INTERVAL            ")> {};
struct OF_MAPS_IN_FILE : seq<Space, Int, Space, IONEX_KEYWORDS("# OF MAPS IN FILE   ")> {};
struct MapFun : rep<4, ascii::alpha> {};
struct MAPPING_FUNCTION : seq<two<' '>, MapFun, rep<54, ascii::any>, IONEX_KEYWORDS("MAPPING FUNCTION    ")> {};
struct ELEVATION_CUTOFF : seq<Space, Float, Space, IONEX_KEYWORDS("ELEVATION CUTOFF    ")> {};
struct OBSERVABLES_USED : seq<rep<60, ascii::any>, IONEX_KEYWORDS("OBSERVABLES USED    ")> {};
struct OF_STATIONS : seq<Space, Int, Space, IONEX_KEYWORDS("# OF STATIONS       ")> {};
struct OF_SATELLITES : seq<Space, Int, Space, IONEX_KEYWORDS("# OF SATELLITES     ")> {};
struct BASE_RADIUS : seq<Space, Float, Space, IONEX_KEYWORDS("BASE RADIUS         ")> {};
struct MAP_DIMENSION : seq<Space, Int, Space, IONEX_KEYWORDS("MAP DIMENSION       ")> {};
struct HGT1_HGT2_DHGT : seq<rep<60, ascii::any>, IONEX_KEYWORDS("HGT1 / HGT2 / DHGT  ")> {};
struct LAT1_LAT2_DLAT   : seq<rep<60, ascii::any>, IONEX_KEYWORDS("LAT1 / LAT2 / DLAT  ")> {};
struct LON1_LON2_DLON   : seq<rep<60, ascii::any>, IONEX_KEYWORDS("LON1 / LON2 / DLON  ")> {};
struct EXPONENT : seq<Space, Int, Space, IONEX_KEYWORDS("EXPONENT            ")> {};

struct HeaderEnd : until<IONEX_KEYWORDS("END OF HEADER       ")> {};

struct HeaderLine : sor<PGM_RUNBY_DATE,
    COMMENT,
    DESCRIPTION,
    EPOCH_OF_FIRST_MAP,
    EPOCH_OF_LAST_MAP,
    INTERVAL,
    OF_MAPS_IN_FILE,
    MAPPING_FUNCTION,
    ELEVATION_CUTOFF,
    OBSERVABLES_USED,
    OF_STATIONS,
    OF_SATELLITES,
    BASE_RADIUS,
    MAP_DIMENSION,
    HGT1_HGT2_DHGT,
    LAT1_LAT2_DLAT,
    LON1_LON2_DLON,
    EXPONENT> {};

struct Header : seq<IONEX_VERSION_TYPE, plus<HeaderLine>, until<HeaderEnd>> {};

struct DataMapId : seq<Space, plus<ascii::digit>, rep<54, one<' '>>> {};
struct TecMapBegin : until<IONEX_KEYWORDS("START OF TEC MAP    "), DataMapId> {};
struct RmsMapBegin : until<IONEX_KEYWORDS("START OF RMS MAP    "), DataMapId> {};
struct HeightMapBegin : until<IONEX_KEYWORDS("START OF HEIGHT MAP "), DataMapId> {};

struct TecMapEnd : until<IONEX_KEYWORDS("END OF TEC MAP      "), DataMapId> {};
struct RmsMapEnd : until<IONEX_KEYWORDS("END OF RMS MAP      "), DataMapId> {};
struct HeightMapEnd : until<IONEX_KEYWORDS("END OF HEIGHT MAP   "), DataMapId> {};

struct EpochOfCurrentMap : until<IONEX_KEYWORDS("EPOCH OF CURRENT MAP"), seq<EPOCH, Space>> {};

struct DataMapLat : Float {};
struct DataMapLon1 : Float {};
struct DataMapLon2 : Float {};
struct DataMapDLon : Float {};
struct DataMapH : Float {};
struct LAT_LON1_LON2_DLON_H : until<IONEX_KEYWORDS("LAT/LON1/LON2/DLON/H"),
    seq<Space, DataMapLat, Space, DataMapLon1, Space, DataMapLon2, Space, DataMapDLon, Space, DataMapH, Space>> {};

struct Data : seq<Space, Int> {};
struct DataFullLine : seq<rep<16, Data>, LineTail> {};
struct DataLastLine : seq<rep<9, Data>, LineTail> {};
struct DataBlock : seq<rep<4, DataFullLine>, DataLastLine> {};

struct TecMap : seq<TecMapBegin, EpochOfCurrentMap, plus<LAT_LON1_LON2_DLON_H, DataBlock>, TecMapEnd> {};
struct TecMaps : plus<TecMap> {};
struct RmsMap : seq<RmsMapBegin, EpochOfCurrentMap, plus<LAT_LON1_LON2_DLON_H, DataBlock>, RmsMapEnd> {};
struct RmsMaps : star<RmsMap> {};
struct HeightMap : seq<HeightMapBegin, EpochOfCurrentMap, plus<LAT_LON1_LON2_DLON_H, DataBlock>, HeightMapEnd> {};
struct HeightMaps : star<HeightMap> {};

struct FileEnd : until<IONEX_KEYWORDS("END OF FILE         "), Space> {};

struct Rule : seq<Header, TecMaps, RmsMaps, HeightMaps, FileEnd> {};

template<typename T>
struct Action {};

// clang-format on
inline std::string& trim(std::string&& str)
{
    std::string blanks(" ");
    str.erase(0, str.find_first_not_of(blanks));
    str.erase(str.find_last_not_of(blanks) + 1);
    return str;
}

template<typename T>
struct IntHelper
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, IonexPaser& p)
    {
        Action<T>::setValue(p, std::stoi(std::string(input.begin(), input.size())));
    }
};

template<typename T>
struct DoubleHelper
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, IonexPaser& p)
    {
        Action<T>::setValue(p, std::stod(std::string(input.begin(), input.size())));
    }
};

template<>
struct Action<Version> : DoubleHelper<Version>
{
    static void setValue(IonexPaser& p, double value) { p.version = value; }
};

template<>
struct Action<Type>
{
    static void apply0(IonexPaser& p) { p.fileType = "Ionosphere maps"; }
};

template<>
struct Action<Satellite>
{
    constexpr static auto enums = { "BEN", "ENV", "ERS", "GEO", "GNS", "IRI", "MIX", "NNS", "TOP" };
    template<typename ParseInput>
    static bool apply(const ParseInput& input, IonexPaser& p)
    {
        std::string str(input.begin(), input.end());
        auto it = std::ranges::find(enums, str);
        if (it == enums.end())
            return false;
        p.system = static_cast<SatelliteSystem>(std::distance(enums.begin(), it));
        return true;
    }
};

template<>
struct Action<PGM_RUNBY_DATE>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, IonexPaser& p)
    {
        std::string str(input.begin(), input.end());
        p.program        = trim(str.substr(0, 20));
        p.agency         = trim(str.substr(20, 20));
        p.createDateTime = trim(str.substr(40, 20));
    }
};

template<>
struct Action<COMMENT>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, IonexPaser& p)
    {
        std::string str(input.begin(), input.end());
        p.comments.push_back(trim(str.substr(0, 60)));
    }
};

template<>
struct Action<DESCRIPTION>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, IonexPaser& p)
    {
        std::string str(input.begin(), input.end());
        p.descriptions.push_back(trim(str.substr(0, 60)));
    }
};

template<>
struct Action<EPOCH_OF_FIRST_MAP>
{
    static void apply0(IonexPaser& p) { p.epochOfFirstMap = p.cache.dateTime; }
};

template<>
struct Action<EPOCH_OF_LAST_MAP>
{
    static void apply0(IonexPaser& p) { p.epochOfLastMap = p.cache.dateTime; }
};

template<>
struct Action<INTERVAL>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, IonexPaser& p)
    {
        std::string str(input.begin(), input.end());
        p.interval = std::stoi(trim(str.substr(0, 60)));
    }
};

template<>
struct Action<OF_MAPS_IN_FILE>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, IonexPaser& p)
    {
        std::string str(input.begin(), input.end());
        p.ofMapsInFile = std::stoi(trim(str.substr(0, 60)));
    }
};

template<>
struct Action<MapFun>
{
    constexpr static auto enums = { "NONE", "COSZ", "QFAC" };

    template<typename ParseInput>
    static bool apply(const ParseInput& input, IonexPaser& p)
    {
        std::string str(input.begin(), input.end());
        auto it = std::ranges::find(enums, str);
        if (it == enums.end())
            return false;
        p.mapingFunction = static_cast<MapingFunction>(std::distance(enums.begin(), it));
        return true;
    }
};

template<>
struct Action<ELEVATION_CUTOFF>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, IonexPaser& p)
    {
        std::string str(input.begin(), input.end());
        p.elevationCutOff = std::stod(trim(str.substr(0, 60)));
    }
};

template<>
struct Action<OBSERVABLES_USED>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, IonexPaser& p)
    {
        std::string str(input.begin(), input.end());
        p.observablesUsed = trim(str.substr(0, 60));
    }
};

template<>
struct Action<OF_STATIONS>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, IonexPaser& p)
    {
        std::string str(input.begin(), input.end());
        p.ofStations = std::stoi(trim(str.substr(0, 60)));
    }
};

template<>
struct Action<OF_SATELLITES>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, IonexPaser& p)
    {
        std::string str(input.begin(), input.end());
        p.ofSatellites = std::stoi(trim(str.substr(0, 60)));
    }
};

template<>
struct Action<BASE_RADIUS>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, IonexPaser& p)
    {
        std::string str(input.begin(), input.end());
        p.baseRadius = std::stod(trim(str.substr(0, 60)));
    }
};

template<>
struct Action<MAP_DIMENSION>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, IonexPaser& p)
    {
        std::string str(input.begin(), input.end());
        p.mapDimension = std::stoi(trim(str.substr(0, 60)));
    }
};

template<>
struct Action<HGT1_HGT2_DHGT>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, IonexPaser& p)
    {
        std::string str(input.begin(), input.end());
        p.hgt1 = std::stod(trim(str.substr(2, 6)));
        p.hgt2 = std::stod(trim(str.substr(8, 6)));
        p.dhgt = std::stod(trim(str.substr(14, 6)));
    }
};

template<>
struct Action<LAT1_LAT2_DLAT>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, IonexPaser& p)
    {
        std::string str(input.begin(), input.end());
        p.lat1 = std::stod(trim(str.substr(2, 6)));
        p.lat2 = std::stod(trim(str.substr(8, 6)));
        p.dlat = std::stod(trim(str.substr(14, 6)));
    }
};

template<>
struct Action<LON1_LON2_DLON>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, IonexPaser& p)
    {
        std::string str(input.begin(), input.end());
        p.lon1 = std::stod(trim(str.substr(2, 6)));
        p.lon2 = std::stod(trim(str.substr(8, 6)));
        p.dlon = std::stod(trim(str.substr(14, 6)));
    }
};

template<>
struct Action<EXPONENT>
{
    template<typename ParseInput>
    static void apply(const ParseInput& input, IonexPaser& p)
    {
        std::string str(input.begin(), input.end());
        p.exponent = std::stoi(trim(str.substr(0, 60)));
    }
};

template<>
struct Action<DataMapId> : IntHelper<DataMapId>
{
    // clang-format off
    static void setValue(IonexPaser& p, int value)
    {
        p.cache.dataMap.id = value;
    }
    // clang-format on
};

template<>
struct Action<Year> : IntHelper<Year>
{
    static void setValue(IonexPaser& p, int value) { p.cache.dateTime.year = value; }
};

template<>
struct Action<Month> : IntHelper<Month>
{
    static void setValue(IonexPaser& p, int value) { p.cache.dateTime.month = value; }
};

template<>
struct Action<Day> : IntHelper<Day>
{
    static void setValue(IonexPaser& p, int value) { p.cache.dateTime.day = value; }
};

template<>
struct Action<Hour> : IntHelper<Hour>
{
    static void setValue(IonexPaser& p, int value) { p.cache.dateTime.hour = value; }
};

template<>
struct Action<Minute> : IntHelper<Minute>
{
    static void setValue(IonexPaser& p, int value) { p.cache.dateTime.minute = value; }
};

template<>
struct Action<Second> : IntHelper<Second>
{
    static void setValue(IonexPaser& p, int value) { p.cache.dateTime.second = value; }
};

template<>
struct Action<DataMapLat> : DoubleHelper<DataMapLat>
{
    static void setValue(IonexPaser& p, double value)
    {
        p.cache.dataMap.blockDatas.push_back({});
        p.cache.dataMap.blockDatas.back().lat = value;
    }
};

template<>
struct Action<DataMapLon1> : DoubleHelper<DataMapLon1>
{
    static void setValue(IonexPaser& p, double value) { p.cache.dataMap.blockDatas.back().lon1 = value; }
};

template<>
struct Action<DataMapLon2> : DoubleHelper<DataMapLon2>
{
    static void setValue(IonexPaser& p, double value) { p.cache.dataMap.blockDatas.back().lon2 = value; }
};

template<>
struct Action<DataMapDLon> : DoubleHelper<DataMapDLon>
{
    static void setValue(IonexPaser& p, double value) { p.cache.dataMap.blockDatas.back().dlon = value; }
};

template<>
struct Action<DataMapH> : DoubleHelper<DataMapH>
{
    static void setValue(IonexPaser& p, double value) { p.cache.dataMap.blockDatas.back().h = value; }
};

template<>
struct Action<Data> : IntHelper<Data>
{
    static void setValue(IonexPaser& p, int value) { p.cache.dataMap.blockDatas.back().datas.push_back(value); }
};

template<>
struct Action<TecMapEnd>
{
    static void apply0(IonexPaser& p)
    {
        p.cache.dataMap.epoch = p.cache.dateTime;
        p.tecMaps.push_back(std::move(p.cache.dataMap));
    }
};

template<>
struct Action<RmsMapEnd>
{
    static void apply0(IonexPaser& p)
    {
        p.cache.dataMap.epoch = p.cache.dateTime;
        p.rmsMaps.push_back(std::move(p.cache.dataMap));
    }
};

template<>
struct Action<HeightMapEnd>
{
    static void apply0(IonexPaser& p)
    {
        p.cache.dataMap.epoch = p.cache.dateTime;
        p.heightMaps.push_back(std::move(p.cache.dataMap));
    }
};
} // namespace PEG
} // namespace Ionex